package chatNI;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JOptionPane;

import chatController.ChatController;
import cs.remoteapp.FileAnswer;
import cs.remoteapp.FileRequest;
import cs.remoteapp.Goodbye;
import cs.remoteapp.Hello;
import cs.remoteapp.TextMessage;

/**
 * <b>Project ChatSystemG2 - INSA 4INFOA1 - 2013/2014</b>
 * <P>
 * <i>Package  chatNI</i> <br>
 * ChatNI is a view in the MVC design pattern. It is also an observer, observing the model. <br>
 * It is the part of the chat system that is related to the network. It is used in sending and receiving the messages. <br>
 *
 * @author Sergio Gonzalez - Marie-Charlotte Bideaud
 */

public class ChatNI extends Thread implements Observer, NIToCont, RemoteApp {

	private ChatController controller;
	private final int size = 1024;
	private ReceiveThread receiveth;
	private boolean running = true;
	
	private InetAddress sourceAddress ; 
	private String path;
	
	private ServerSocket servSocket;
	private DatagramSocket udpSendSocket;
	private DatagramSocket udpRecvSocket;
	
	private FileSender fileSender;
	private FileReceiver fileReceiver;
	
	/**
	 * Constructor for the class ChatNI
	 * 
	 * @param controller : ChatController
	 * @throws IOException
	 */
	public ChatNI (ChatController controller) throws IOException {
		this.controller = controller;
			this.servSocket = new ServerSocket(this.controller.getLocaluser().getTcpPort());
			this.udpSendSocket = new DatagramSocket();
			this.udpRecvSocket = new DatagramSocket(this.controller.getLocaluser().getUdpPort());
	}

	/**
	 * Method starting the thread
	 */
	public void run () {
		while (running){
			try {
				processReceive();
			}
			catch (IOException e) {
				JOptionPane.showMessageDialog(null,
						"An error occured during the reception.",
						"Reception error",JOptionPane.ERROR_MESSAGE);
				controller.performDisconnect();
			}
		}
	}

	/**
	 * Method called when an observed object gets changed
	 * 
	 * @param o : Observable
	 * @param arg : Object
	 */
	public void update(Observable o, Object arg) {
		if (arg instanceof InetAddress) {
			setSourceAddress ( (InetAddress) arg );
		}
		if (arg instanceof Hello) {
			try {
				hello(arg);
			} catch (UnknownHostException e) {
				JOptionPane.showMessageDialog(null,
					    "An error occured during the broadcast sending.",
					    "Broadcast error",JOptionPane.ERROR_MESSAGE);
				controller.performDisconnect();
			} catch (IOException e) {
				JOptionPane.showMessageDialog(null,
					    "An error occured during the sending.",
					    "Sending error",JOptionPane.ERROR_MESSAGE);
				controller.performDisconnect();
			}
		}
		if (arg instanceof Goodbye) {
			try {
				goodbye(arg);
			} catch (UnknownHostException e) {
				JOptionPane.showMessageDialog(null,
					    "An error occured during the broadcast sending.",
					    "Broadcast error",JOptionPane.ERROR_MESSAGE);
				controller.performDisconnect();
			} catch (IOException e) {
				JOptionPane.showMessageDialog(null,
					    "An error occured during the sending.",
					    "Sending error",JOptionPane.ERROR_MESSAGE);
				controller.performDisconnect();
			}
		}
		if (arg instanceof TextMessage) {
			try {
				sendText(arg);
			} catch (IOException e) {
				JOptionPane.showMessageDialog(null,
					    "An error occured during the sending.",
					    "Sending error",JOptionPane.ERROR_MESSAGE);
				controller.performDisconnect();
			}
		}
		if (arg instanceof FileRequest) {
			try {
				fileRequest(arg);
			} catch (IOException e) {
				JOptionPane.showMessageDialog(null,
					    "An error occured during the sending.",
					    "Sending error",JOptionPane.ERROR_MESSAGE);
				controller.performDisconnect();
			}
		}
		if (arg instanceof FileAnswer) {
			try {
				fileAnswer(arg);
			} catch (IOException e) {
				JOptionPane.showMessageDialog(null,
					    "An error occured during the sending.",
					    "Sending error",JOptionPane.ERROR_MESSAGE);
				controller.performDisconnect();
			}
		}
		if (arg instanceof String) {
			setPath((String) arg);
		}
	}
	
	/**
	 * Datagrams reception method
	 */
	public void processReceive() throws IOException {
		while (running) {
			byte buffer[] = new byte[size];
			DatagramPacket packet =
					new DatagramPacket(buffer, buffer.length);

			udpRecvSocket.receive(packet);
			receiveth = new ReceiveThread(packet, controller);
			receiveth.start();
		}
	}
	
	/**
	 * Method that creates a hello message 
	 * 
	 * @param arg : Object
	 * @throws UnknownHostException
	 * @throws IOException 
	 */
	public void hello(Object arg) throws UnknownHostException, IOException {
		DatagramPacket packet = null;
		Hello testhello = (Hello) arg;
		if (testhello.isBroadcast()) {
			packet = sendBroadcast (arg);
		} else {
			packet = send1To1 (arg);
		}
		sendHello(packet);
	}
	
	/**
	 * Method that sends a hello message using UDP
	 * 
	 * @param packet : DatagramPacket
	 * @throws IOException 
	 */
	public void sendHello(DatagramPacket packet) throws IOException {
		sendUdp(packet);
	}

	/**
	 * Method that creates a goodbye message 
	 * 
	 * @param arg : Object
	 * @throws UnknownHostException
	 * @throws IOException 
	 */
	public void goodbye(Object arg) throws UnknownHostException, IOException {
		DatagramPacket packet = sendBroadcast (arg);
		sendGoodbye(packet);
	}
	
	/**
	 * Method that sends a goodbye message using UDP
	 * 
	 * @param packet : DatagramPacket
	 * @throws IOException 
	 */
	public void sendGoodbye(DatagramPacket packet) throws IOException {
		sendUdp(packet);
	}

	/**
	 * Method that creates a text message 
	 * 
	 * @param arg : Object
	 * @throws IOException 
	 */
	public void sendText(Object arg) throws IOException {
		DatagramPacket packet = send1To1 (arg);
		sendTextMsg(packet);
	}
	
	/**
	 * Method that sends a text message using UDP
	 * 
	 * @param packet : DatagramPacket
	 * @throws IOException 
	 */
	public void sendTextMsg(DatagramPacket packet) throws IOException {
		sendUdp(packet);
	}

	/**
	 * Method that creates a file request 
	 * 
	 * @param arg : Object
	 * @throws IOException 
	 */
	public void fileRequest(Object arg) throws IOException {
		FileRequest fileRequest = (FileRequest) arg;
		fileSender = new FileSender();
		fileSender.setSourceAddress(this.getSourceAddress());
		fileSender.setPort(controller.getLocaluser().getTcpPort());
		fileSender.setFileSize(fileRequest.getFileSize());
		fileSender.setPath(fileRequest.getFileName());
		DatagramPacket packet = send1To1 (arg);
		sendFileRequest(packet);
	}
	
	/**
	 * Method that sends a file request using UDP
	 * 
	 * @param packet : DatagramPacket
	 * @throws IOException 
	 */
	public void sendFileRequest(DatagramPacket packet) throws IOException {
		sendUdp(packet);
	}
	
	/**
	 * Method that creates a file answer 
	 * 
	 * @param arg : Object
	 * @throws IOException 
	 */
	public void fileAnswer(Object arg) throws IOException {
		FileAnswer fileAnswer = (FileAnswer) arg;
		if (fileAnswer.isAccepted()) {
			fileReceiver = new FileReceiver ( servSocket, fileAnswer.getFileName(), getPath() );
			fileReceiver.start();
		}
		DatagramPacket packet = send1To1 (arg);
		sendFileRequest(packet);
	}
	
	/**
	 * Method that sends a file answer using UDP 
	 * 
	 * @param packet : DatagramPacket
	 * @throws IOException 
	 */
	public void sendFileAnswer(DatagramPacket packet) throws IOException {
		sendUdp(packet);
	}
	
	/**
	 * Method that sends a file using TCP 
	 */
	synchronized public void sendFile () {
		fileSender.start();
	}

	/**
	 * Method called to close the receive threads
	 * 
	 * @throws InterruptedException
	 */
	public void closeThreads() throws InterruptedException{
		running = false;
		receiveth.join();
	}
	
	/**
	 * Method called to close the sockets
	 * 
	 * @throws IOException
	 */
	public void closeSockets() throws IOException {
		udpRecvSocket.disconnect();
		udpSendSocket.close();
		udpRecvSocket.close();
		servSocket.close();
	}
	
	/**
	 * Method called when a user sends a message to a remote user (not in broadcast)
	 * 
	 * @param arg : Object
	 * @return DatagramPacket
	 * @throws IOException
	 */
	public DatagramPacket send1To1 (Object arg) throws IOException {
		
		byte[] msg = writeBuffer(arg);
		DatagramPacket packet = null;
		packet = new DatagramPacket(msg, msg.length,
				getSourceAddress(), controller.getLocaluser().getUdpPort());
		return packet;
	}
	
	/**
	 * Method called when a user sends a broadcasted message
	 * 
	 * @param arg : Object
	 * @return DatagramPacket
	 * @throws IOException
	 */
	public DatagramPacket sendBroadcast (Object arg) throws IOException {
		// controller.getLocaluser().getBroadcast()
		byte[] msg = writeBuffer(arg);
		DatagramPacket packet = null;
		packet = new DatagramPacket(msg, msg.length,
				InetAddress.getByName("255.255.255.255"), controller.getLocaluser().getUdpPort());
		
		return packet;
	}

	/**
	 * Method called to write an object in an ObjectOutputStream
	 * 
	 * @param arg : Object
	 * @return byte[]
	 * @throws IOException
	 */
	public byte[] writeBuffer(Object arg) throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream() ;
		ObjectOutputStream oos = null;
		oos = new ObjectOutputStream(bos);

		// serialization: writing of the object in the output stream
		oos.writeObject(arg);

		// cleaning the buffer
		oos.flush();
		
		return bos.toByteArray();
	}
	
	/**
	 * Method called to send a message using the UDP protocol
	 * 
	 * @param packet : DatagramPacket
	 * @throws IOException
	 */
	public void sendUdp (DatagramPacket packet) throws IOException {
			udpSendSocket.send(packet);
	}

	/**
	 * Getter for sourceAddress
	 * 
	 * @return InetAddress
	 */
	public InetAddress getSourceAddress() {
		return sourceAddress;
	}

	/**
	 * Setter of sourceAddress
	 * 
	 * @param sourceAddress : InetAddress
	 */
	public void setSourceAddress(InetAddress sourceAddress) {
		this.sourceAddress = sourceAddress;
	}

	/**
	 * Getter for path
	 * 
	 * @return String
	 */
	public String getPath() {
		return path;
	}

	/**
	 * Setter of path
	 * 
	 * @param path : String
	 */
	public void setPath(String path) {
		this.path = path;
	}

}
